/*
 * Copyright (c) 2008, Xiong Yingfei, the University of Tokyo All rights
 * reserved.
 */
package ac.jp.u_tokyo.SyncLib.language2;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.antlr.runtime.RecognitionException;

import ac.jp.u_tokyo.SyncLib.Mod;
import ac.jp.u_tokyo.SyncLib.NullMod;
import ac.jp.u_tokyo.SyncLib.PrimMod;
import ac.jp.u_tokyo.SyncLib.Sync;
import ac.jp.u_tokyo.SyncLib.SyncFactory;
import ac.jp.u_tokyo.SyncLib.SynchronizationFailedException;
import ac.jp.u_tokyo.SyncLib.dictionaries.DictMod;
import ac.jp.u_tokyo.SyncLib.dictionaries.DictModFactory;
import ac.jp.u_tokyo.SyncLib.dictionaries.DynamicGet;
import ac.jp.u_tokyo.SyncLib.interactive.TraceAspect;
import ac.jp.u_tokyo.SyncLib.modParser.ModParser;
import ac.jp.u_tokyo.SyncLib.util.Helper;
import ac.jp.u_tokyo.SyncLib.valueParser.ValueParser;

public class Console
{
	private static final String NEW_KEY_PREFIX = "__new";

	public static Sync findSyncByName(Class syncFactoryClass, String name)
	{
		Method m;
		try
		{
			m = syncFactoryClass.getMethod("get" + name + "Factory");
			SyncFactory factory = (SyncFactory) m.invoke(null);
			return factory.create();
		}
		catch (Exception e)
		{
			return null;
		}
	}

	private static final String usage = "command: \n" + "name\n" + "val\n"
			+ "depth number\n" +
					"switch synchronizerName\n" + "sync modifcations\n"
			+ "resync values; modifications\n" + "trace [off|on]\n" + "exit";

	public static void interactiveExec(Class syncFactoryClass)
			throws IOException
	{
		Map<String, Object[]> vals = new HashMap<String, Object[]>();
		String curSync = null;
		Map<String, Sync> syncs = new HashMap<String, Sync>();
		Map<String, Boolean> isInitialized = new HashMap<String, Boolean>();

		{
			String newSyncName = "main";
			curSync = switchTo(syncFactoryClass, curSync, newSyncName, vals,
					syncs, isInitialized);
		}

		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		while (true)
		{
			System.out.print(">>");
			String input = br.readLine();
			String command = input.split("[ \t]")[0].trim();
			
			if (command.compareToIgnoreCase("exit") == 0)
			{
				return;
			}
			else if (command.compareToIgnoreCase("trace") == 0)
			{
				if (input.contains("on"))
					TraceAspect._showTrace = true;
				else
					TraceAspect._showTrace = false;
				continue;
			}
			else if (command.compareToIgnoreCase("depth") == 0)
			{
				String level = input.split("[ \t]")[1].trim();
				try {
					TraceAspect._maxLevels = new Integer(level);
				}
				catch(NumberFormatException err) {
					TraceAspect._maxLevels = Integer.MAX_VALUE;
				}
				continue;
			}

			
			if (curSync == null
					&& (command.compareToIgnoreCase("sync") == 0
							|| command.compareToIgnoreCase("resync") == 0
							|| command.compareToIgnoreCase("name") == 0 || command
							.compareToIgnoreCase("val") == 0))
			{
				System.out.println("no synchronizer loaded.");
				continue;
			}
			if (!isInitialized.get(curSync)
					&& (command.compareToIgnoreCase("sync") == 0 || command
							.compareToIgnoreCase("val") == 0))
			{
				System.out.println("execute resync first.");
				continue;
			}

			if (command.compareToIgnoreCase("val") == 0)
			{
				System.out.println(Helper.toString(vals.get(curSync)));
			}
			else if (command.compareToIgnoreCase("name") == 0)
			{
				System.out.println(curSync);
			}
			else if (command.compareToIgnoreCase("switch") == 0)
			{
				String newSyncName;
				try
				{
					newSyncName = input.split("[ \t]")[1].trim();
				}
				catch (ArrayIndexOutOfBoundsException e)
				{
					System.out.println(usage);
					continue;
				}

				if (syncs.containsKey(newSyncName))
				{
					curSync = newSyncName;
				}
				else
				{
					curSync = switchTo(syncFactoryClass, curSync, newSyncName,
							vals, syncs, isInitialized);
				}
			}
			else if (command.compareToIgnoreCase("sync") == 0)
			{
				Mod[] mods;
				try
				{
					mods = ModParser.parse(input.substring(command.length()));
					mods = convertMods(mods, vals.get(curSync));
					if (mods == null) continue;
					Mod[] result = syncs.get(curSync).synchronize(mods);
					Helper.applyMods(vals.get(curSync), result);
					System.out.println(Helper.toString(result));
				}
				catch (RecognitionException e)
				{
					System.out.println("cannot read input modification!");
//					if (showTrace()) e.printStackTrace(System.out);
				}
				catch (SynchronizationFailedException e)
				{
					System.out.println("Synchronization failed!");
//					if (showTrace()) e.printStackTrace(System.out);
				}
			}
			else if (command.compareToIgnoreCase("resync") == 0)
			{
				Mod[] mods;
				String modString;
				String valueString;
				try
				{
					modString = input.substring(command.length()).split(";")[1];
					valueString = input.substring(command.length()).split(";")[0];
				}
				catch (ArrayIndexOutOfBoundsException e)
				{
					System.out.println(usage);
					continue;
				}

				try
				{
					mods = ModParser.parse(modString);
					Object[] values = ValueParser.parse(valueString);
					mods = convertMods(mods, values);
					if (mods == null) continue;

					Mod[] result = syncs.get(curSync).resynchronize(values,
							mods);
					vals.put(curSync, values);
					Helper.applyMods(vals.get(curSync), result);
					System.out.println(Helper.toString(result));
					isInitialized.put(curSync, true);
				}
				catch (RecognitionException e)
				{
					System.out.println("cannot read input modification!");
//					if (showTrace()) e.printStackTrace(System.out);
				}
				catch (SynchronizationFailedException e)
				{
					System.out.println("Synchronization failed!");
//					if (showTrace()) e.printStackTrace(System.out);
				}
			}
			else
			{
				System.out.println(usage);
			}
		}
	}

	private static String switchTo(Class syncFactoryClass, String curSync,
			String newSyncName, Map<String, Object[]> vals,
			Map<String, Sync> syncs, Map<String, Boolean> isInitialized)
	{
		Sync s = findSyncByName(syncFactoryClass, newSyncName);
		if (s == null)
		{
			System.out.println("Cannot create synchronizer " + newSyncName);
		}
		else
		{
			curSync = newSyncName;
			syncs.put(curSync, s);
			vals.put(curSync, new Object[s.getParaCount()]);
			isInitialized.put(curSync, false);
		}
		return curSync;
	}

	private static Mod[] convertMods(Mod[] mods, Object[] values)
	{
		Map<String, Integer> newKeyMap = new HashMap<String, Integer>();
		Mod[] result = new Mod[mods.length];
		for (int i = 0; i < values.length; i++)
		{
			result[i] = convertMod(mods[i], values[i], newKeyMap);
			if (result[i] == null) return null;
		}
		return result;
	}

	private static Mod convertMod(Mod mod, Object value,
			Map<String, Integer> newKeyMap)
	{
		if (mod instanceof PrimMod)
		{
			PrimMod pmod = (PrimMod) mod;
			if (value instanceof Map && pmod.getNewValue() != null)
			{
				ReportErr("Type mismatch.", mod, value);
				return null;
			}
			else if (pmod.getNewValue() instanceof String && ((String)pmod.getNewValue()).startsWith(NEW_KEY_PREFIX)) {
				return new PrimMod<Object>(convertKey(newKeyMap, (String) pmod.getNewValue()));
			}
			return mod;
		}
		else if (mod instanceof NullMod)
		{
			return mod;
		}
		else
		{
			assert mod instanceof DictMod;
			if (value != null && false == value instanceof Map )
			{
				ReportErr("Type mismatch.", mod, value);
				return null;
			}
			DictMod<Object, Object> old = (DictMod) mod;
			Map<Object, Object> dictValue = (Map<Object, Object>) value;
			if (dictValue == null) dictValue = new HashMap<Object, Object>();

			DictModFactory<Object, Object> result = new DictModFactory<Object, Object>();

			for (Entry<Object, Mod<Object>> entry : old)
			{
				Object key;
				Mod newMod;
				if (entry.getKey() instanceof String
						&& ((String) entry.getKey()).startsWith(NEW_KEY_PREFIX))
				{
					key = convertKey(newKeyMap, (String) entry.getKey());
				}
				else
					key = entry.getKey();
				Object innerValue = dictValue.get(key);
				newMod = convertMod(entry.getValue(), innerValue, newKeyMap);
				result.addPut(key, newMod);
				
			}
			return result.create();
		}
	}

	private static Object convertKey(Map<String, Integer> newKeyMap,
			String newKey)
	{
		Object key;
		key = newKeyMap.get(newKey);
		if (key == null)
		{
			key = new GlobalIncrementalFactory().createNewKey();
			newKeyMap.put(newKey, (Integer) key);
			System.out.println(newKey + '=' + key);
		}
		return key;
	}

	//	private static Mod[] createNullMods(int length)
	//	{
	//		Mod[] mods = new Mod[length];
	//		for (int i = 0; i < length; i++)
	//		{
	//			mods[i] = NullMod.INSTANCE;
	//		}
	//		return mods;
	//	}

	private static void ReportErr(String string, Mod mod, Object value)
	{
		System.out.println(string + " mod:" + Helper.toString(mod)+ ", value:"
				+ Helper.toString(value));

	}

	public static void main(String[] args) throws IOException
	{
		Console.interactiveExec(Console.class);
	}
}
